using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;


namespace Zsa.Emulation.Memory
{
    /// <summary>
    /// Maps memory.
    /// </summary>
    public abstract class MemoryMap 
    {
        #region Fields
        /// <summary>
        /// The memory items.
        /// </summary>
        private IMemoryStream[] _MemoryStreams;

        /// <summary>
        /// Memory lookup table.
        /// </summary>
        private MemoryBlock[] _LookupTable;

        /// <summary>
        /// The memory blocks.
        /// </summary>
        private MemoryBlock[] _MemoryBlocks;
        private int _MapOffset;
        #endregion Fields

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="T:MemoryMap"/> class.
        /// </summary>
        public MemoryMap()
        {
        }
        #endregion Constructor

        #region Methods

        #region Init Methods
        /// <summary>
        /// Inits this instance.
        /// </summary>
        public void Init()
        {
            // Get the memory items.
            MemoryItem[] _MemoryItems = OnInit();
            // Create the streams.
            _MemoryStreams = new IMemoryStream[_MemoryItems.Length];
            // Create a list for blocks.
            List<MemoryBlock> _Blocks = new List<MemoryBlock>(_MemoryItems.Length*2);
            // Temp var for blocks.
            MemoryBlock[] _MemBlocks;
            // The block.
            MemoryBlock _BlockTemp;
            // Loop through the memory items.
            for (int _MemoryItemIndex = 0; _MemoryItemIndex < _MemoryItems.Length; _MemoryItemIndex++)
            {
                // Set the memory item.
                _MemoryStreams[_MemoryItemIndex] = _MemoryItems[_MemoryItemIndex].Memory;
                // Get the blocks.
                _MemBlocks = _MemoryItems[_MemoryItemIndex].Blocks;
                // Set the index for the blocks.
                for (int _MemBlockIndex = 0; _MemBlockIndex < _MemBlocks.Length; _MemBlockIndex++)
                {
                    // Copy the block from the array.
                    _BlockTemp = _MemBlocks[_MemBlockIndex];
                    // Add the block to the collection.
                    _Blocks.Add(new MemoryBlock(_BlockTemp.BlockAddress, _BlockTemp.BlockLength,
                        _BlockTemp._BlockOffset, (ushort)_MemoryItemIndex));
                }
            }
            // Set the memory blocks.
            _MemoryBlocks = _Blocks.ToArray();

            int startAddress = _MemoryBlocks[0].Address;
            int endAddress = _MemoryBlocks[_MemoryBlocks.Length - 1].EndAddress;
            for (int i = 0; i < _MemoryBlocks.Length; i++)
            {
                if (endAddress < _MemoryBlocks[i].EndAddress)
                {
                    endAddress = _MemoryBlocks[i].EndAddress;
                }
            }


            _MapOffset = startAddress;
            _LookupTable = new MemoryBlock[(endAddress-startAddress)+1];
            for (int i = startAddress; i < endAddress; i++)
            {
                for (int x = 0; x < _MemoryBlocks.Length; x++)
                {
                    if (_MemoryBlocks[x].ContainsAddress(i))
                    {
                        try
                        {
                            _LookupTable[i - _MapOffset] = _MemoryBlocks[x];
                        }
                        catch (Exception ex)
                        {
                            throw ex;
                        }
                    }
                }
            }

        }

        /// <summary>
        /// Resets the memory.
        /// </summary>
        protected abstract MemoryItem[] OnInit();

        #endregion Init Methods

        #region Read Methods
        

        /// <summary>
        /// Reads an byte from the stream.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <returns></returns>
        public virtual byte Read(ushort address)
        {
            try
            {
                int offsetAddress = address - _MapOffset;
                MemoryBlock item = _LookupTable[offsetAddress];
                int sourceIndex = item._BlockIndex;
                offsetAddress = address - item._BlockOffset;
                return _MemoryStreams[sourceIndex].Read(offsetAddress);
            }
            catch (Exception excep)
            {
                throw excep;
            }
        }

        /// <summary>
        /// Reads the specified address.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <returns></returns>
        public virtual byte Read(int address)
        {
            try
            {
                int offsetAddress = address - _MapOffset;
                MemoryBlock item = _LookupTable[offsetAddress];
                int sourceIndex = item._BlockIndex;
                offsetAddress = address - item._BlockOffset;
                return _MemoryStreams[sourceIndex].Read(offsetAddress);
            }
            catch (Exception excep)
            {
                throw excep;
            }
        }

        /// <summary>
        /// Reads the specified buffer.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <param name="address">The address.</param>
        /// <param name="count">The count.</param>
        public void Read(byte[] buffer, ushort address, ushort count)
        {

            MemoryBlock item;
            int bytesLeft;
            int bytesRead;
            int blockOffset;
            int blockIndex;
            int offsetAddress;            

            item = _LookupTable[address];
            bytesLeft = count;
            blockOffset = item._BlockOffset;
            blockIndex = item._BlockIndex;
            offsetAddress = address - blockOffset;
            bytesRead = item.GetLength(address, (ushort)bytesLeft);

            _MemoryStreams[blockIndex].Read(buffer, offsetAddress, bytesRead);
            address += (ushort)bytesRead;
            bytesLeft -= bytesRead;

            while (bytesLeft > 0)
            {
                item = _LookupTable[address];
                bytesRead = item.GetLength(address, (ushort)bytesLeft);
                blockOffset = item._BlockOffset;
                blockIndex = item._BlockIndex;
                offsetAddress = address - blockOffset;

                _MemoryStreams[blockIndex].Read(buffer, offsetAddress, bytesRead);
                address += (ushort)bytesRead;
                bytesLeft -= bytesRead;
            }
            
        }
        #endregion Read Methods

        #region Write Methods
        /// <summary>
        /// Writes the specified buffer.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <param name="address">The address.</param>
        /// <param name="count">The count.</param>
        public virtual void Write(byte[] buffer, int address, int count)
        {
            try
            {
                // Get the item from the lookup table.
                MemoryBlock item = _LookupTable[address];
                int bytesLeft = count;
                int bytesRead = item.GetLength((ushort)address, (ushort)bytesLeft);
                _MemoryStreams[item._BlockIndex].Write(buffer, address - item._BlockOffset, bytesLeft);
                address += (ushort)bytesRead;
                bytesLeft -= count;

                while (bytesLeft > 0)
                {
                    item = _LookupTable[address];
                    bytesRead = item.GetLength((ushort)address, (ushort)bytesLeft);
                    _MemoryStreams[item._BlockIndex].Write(buffer, address - item._BlockOffset, bytesLeft);
                    address += (ushort)bytesRead;
                    bytesLeft -= count;
                }
            }
            catch (Exception excep)
            {
                throw excep;
            }
        }

        /// <summary>
        /// Writes the specified address.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <param name="data">The data.</param>
        public virtual void Write(int address, byte data)
        {
            try
            {
                MemoryBlock item = _LookupTable[address - _MapOffset];
                _MemoryStreams[item.SourceIndex].Write((ushort)address - item._BlockOffset,data);
                return;
            }
            catch (Exception excep)
            {
                throw excep;
            }

            IMemoryStream _Memory = null;
            for (int _TempIndex = 0; _TempIndex < _MemoryBlocks.Length; _TempIndex++)
            {
                if (_MemoryBlocks[_TempIndex].ContainsAddress(address))
                {
                    _Memory = _MemoryStreams[_MemoryBlocks[_TempIndex]._BlockIndex];
                    _Memory.Write(address - _MemoryBlocks[_TempIndex]._BlockOffset, data);
                    return;
                }
            }
            throw new ArgumentOutOfRangeException(
                "address",
                address,
                string.Format("Unable to write to address : {0:X4} Data : {1:X2}.  Out of range !",address,data)
                );
        }
        #endregion Write Methods

        #endregion Methods

       
    }

    
}
